package strategies;

import com.dukascopy.api.IAccount;
import com.dukascopy.api.IBar;
import com.dukascopy.api.IContext;
import com.dukascopy.api.IEngine;
import com.dukascopy.api.IMessage;
import com.dukascopy.api.IOrder;
import com.dukascopy.api.IStrategy;
import com.dukascopy.api.ITick;
import com.dukascopy.api.Instrument;
import com.dukascopy.api.JFException;
import com.dukascopy.api.Period;

import java.io.FileNotFoundException;
import java.io.PrintWriter;

import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;


public abstract class Strategy implements IStrategy {
    public static String workingDirectory = "/home/sanjeev";
    public static String reportFileName = "Report.csv";
    public static final double BREACH_THRESHOLD = 0.0004;
    public static final double BROKERAGE = 0.00015;
    public static int STEADYSTATE_THRESHOLD = 2880; // Corresponds to 10 weekdays
                                                    // of candles when period=5
                                                    // Order specific members // mins

    // Miscellaneous
    protected static boolean isWeekend = false;
    protected static List<IOrder> orders = new ArrayList<IOrder>();
    IAccount account;

    // Strategy arguments
    public StrategyArgs arguments;
    protected IContext context = null;
    protected IEngine engine = null;
    protected int tagCounter = 0;
    protected Instrument localInstrument = null;
    protected ITick latestTick = null;
    protected IBar latestAskBar = null;
    protected IBar latestBidBar = null;
    protected double orderOpenPrice = 0.0;
    protected double orderClosePrice = 0.0;
    protected long orderOpenTime = 0;
    protected long orderCloseTime = 0;
    protected double daysProfit = 0;
    protected double longEntryValue = 0.0;
    protected double shortEntryValue = 0.0;
    protected double longExitValue = 0.0;
    protected double shortExitValue = 0.0;
    protected int numOfCandles = 0;

    // State variables
    protected States state = States.START;
    protected Set<States> stateSet = new HashSet<States>();
    protected boolean tradingZone = true;
    protected boolean steadyState = false;
    protected int conseqLongs = 0;
    protected int conseqShorts = 0;
    protected HashMap<String,PrintWriter> outputFiles = new HashMap<String,PrintWriter>();

    public Strategy(StrategyArgs args) {
        this.arguments = args;
    }

    public String getName() {
        return getClass().getSimpleName();
    }

    public StrategyArgs getArguments() {
        return arguments;
    }

    // IStrategy implementation
    public void onStart(IContext context) throws JFException {
        this.context = context;
        engine = context.getEngine();
        initStates();
        initSarLines();
    }

    public void onStop() throws JFException {
    }

    double getOrderAmount() {
        // determine the amount to be placed in the order.
        // Order has to be in multiples of 1000s of primary currency
        // Note that order is in millions. Hence division by 1000000
        if (arguments.isCompoundedTrade()) {
            int factor = ((int) account.getEquity()) / 100;
            double orderAmount = ((double) (factor * 100 * 10)) / 1000000;

            return orderAmount;
        } else {
            return (arguments.getInitialDeposit() * 10) / 1000000;
        }
    }

    protected double buy(Instrument instrument) throws JFException {
        if (state != States.LONG) {
            IOrder submitOrder = engine.submitOrder(getLabel(instrument),
                    instrument, IEngine.OrderCommand.BUY, getOrderAmount());

            if (!orders.contains(submitOrder)) {
                orders.add(submitOrder);
            }

            orderOpenPrice = latestTick.getAsk();
            orderOpenTime = latestTick.getTime();
            state = States.LONG;
            conseqLongs++;
            conseqShorts = 0;

            /*
             * context.getConsole() .getOut() .println( (new
             * Date(orderOpenTime)).toGMTString() + "," + state.toString() + ","
             * + orderOpenPrice + "," + longExitValue);
             */
        }

        return orderOpenPrice;
    }

    protected double sell(Instrument instrument) throws JFException {
        if (state != States.SHORT) {
            IOrder order = engine.submitOrder(getLabel(instrument), instrument,
                    IEngine.OrderCommand.SELL, getOrderAmount());

            if (!orders.contains(order)) {
                orders.add(order);
            }

            orderOpenPrice = latestTick.getBid();
            orderOpenTime = latestTick.getTime();
            state = States.SHORT;
            conseqShorts++;
            conseqLongs = 0;
        }

        return orderOpenPrice;
    }

    // count opened positions
    protected int positionsTotal(Instrument instrument)
        throws JFException {
        int counter = 0;

        for (IOrder order : engine.getOrders(instrument)) {
            if (order.getState() == IOrder.State.FILLED) {
                counter++;
            }
        }

        return counter;
    }

    protected String getLabel(Instrument instrument) {
        String label = instrument.name();
        label = label.substring(0, 2) + label.substring(3, 5);
        label = label + (tagCounter++);
        label = label.toLowerCase();

        return label;
    }

    public void onTick(Instrument instrument, ITick tick)
        throws JFException {
        if (instrument == Instrument.EURUSD) {
            localInstrument = instrument;
            latestTick = tick;

            // outputString(tick.toString(),true, "ticks.csv");
            // dont compute indicators on weekends
            if (isWeekend(latestTick.getTime())) {
                return;
            }

            evaluateTradeSignal();
            computeSar(false);

            // Compute for the same candle...
        }
    }

    public void onBar(Instrument instrument, Period period, IBar askbar,
        IBar bidbar) throws JFException {
        if ((instrument == Instrument.EURUSD) &&
                (period == arguments.getPeriod())) {
            latestAskBar = askbar;
            latestBidBar = bidbar;
            localInstrument = instrument;

            // outputString(latestAskBar.toString(),true, "bars.csv");
            // dont do ANY computations including indicators on weekends
            if (isWeekend(latestBidBar.getTime())) {
                return;
            }

            // Let the indicators reach the steady state..
            computeSteadyState();
            computeSar(true);
        }
    }

    public void onMessage(IMessage message) throws JFException {
    }

    public void onAccount(IAccount account) throws JFException {
        this.account = account;
    }

    // Class specific function
    protected void initSarLines() {
        SarLine.sarMap.put(this, new HashMap<String,SarLine>());

        SarLine.sarMap.get(this)
                      .put("USL",
            new SarLine(arguments.getAF(), SarLine.LineType.UPPER,
                BREACH_THRESHOLD));
        SarLine.sarMap.get(this)
                      .put("UFL",
            new SarLine(arguments.getEAF(), SarLine.LineType.UPPER,
                BREACH_THRESHOLD));
        SarLine.sarMap.get(this)
                      .put("UXL",
            new SarLine(arguments.getXAF(), SarLine.LineType.UPPER,
                BREACH_THRESHOLD));

        SarLine.sarMap.get(this)
                      .put("LSL",
            new SarLine(arguments.getAF(), SarLine.LineType.LOWER,
                BREACH_THRESHOLD));
        SarLine.sarMap.get(this)
                      .put("LFL",
            new SarLine(arguments.getEAF(), SarLine.LineType.LOWER,
                BREACH_THRESHOLD));
        SarLine.sarMap.get(this)
                      .put("LXL",
            new SarLine(arguments.getXAF(), SarLine.LineType.UPPER,
                BREACH_THRESHOLD));
    }

    protected void initStates() {
        stateSet.add(States.START);
        stateSet.add(States.SHORT);
        stateSet.add(States.LONG);
        stateSet.add(States.NEXT_ONLY_LONG);
        stateSet.add(States.NEXT_ONLY_SHORT);
        stateSet.add(States.LOOK_FOR_LONG);
        stateSet.add(States.LOOK_FOR_SHORT);
        stateSet.add(States.READY_TOGO_LONG);
        stateSet.add(States.READY_TOGO_SHORT);
    }

    protected void computeSar(boolean onBar) {
        Iterator<String> iter = SarLine.sarMap.get(this).keySet().iterator();

        while (iter.hasNext()) {
            if (onBar) {
                SarLine.sarMap.get(this).get(iter.next())
                              .computeSar(latestAskBar, latestBidBar);
            } else {
                SarLine.sarMap.get(this).get(iter.next()).computeSar(latestTick);
            }
        }
    }

    // This fn has to be called only from onBar
    protected void computeSteadyState() {
        if (!steadyState) {
            numOfCandles++;

            if (numOfCandles >= STEADYSTATE_THRESHOLD) {
                steadyState = true;

                Date steadyStateDate = new Date(latestAskBar.getTime());
                context.getConsole().getOut()
                       .println("Steady State Reached at " +
                    steadyStateDate.toGMTString());
            }
        }
    }

    public void evaluateTradeSignal() throws JFException {
        evaluateTradeExit();

        if (steadyState && tradingZone) {
            evaluateTradeEntry();
        }
    }

    /**
     * Evaluates trade entry
     * @throws JFException
     */
    abstract protected void evaluateTradeEntry() throws JFException;

    /**
     * Evaluate trade exit
     * @throws JFException
     */
    abstract protected void evaluateTradeExit() throws JFException;

    public String writeOrders(String fileName) {
        try {
            if (fileName == "") {
                fileName = reportFileName;
            }

            SimpleDateFormat dateFormat = new SimpleDateFormat(
                    "yyyy.MM.dd HH:mm:ss");

            PrintWriter writer = new PrintWriter(fileName);
            double deposit = arguments.getInitialDeposit();

            for (IOrder order : orders) {
                deposit += order.getProfitLossInUSD();
                writer.println(order.getAmount() + "," +
                    order.getOrderCommand() + "," + order.getOpenPrice() + "," +
                    order.getClosePrice() + "," + order.getProfitLossInUSD() +
                    "," + order.getProfitLossInPips() + "," +
                    dateFormat.format(
                        new Date(order.getFillTime() -
                            (long) (5.5 * 60 * 60 * 1000))) + "," +
                    dateFormat.format(
                        new Date(order.getCloseTime() -
                            (long) (5.5 * 60 * 60 * 1000))) + "," +
                    order.getFillTime() + "," + order.getCloseTime() + "," +
                    deposit);
            }

            writer.flush();

            return fileName;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static boolean isWeekend(long time) {
        Date tempDate = new Date(time);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(tempDate);

        // Stop trading for weekends..
        if (((calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) &&
                (((calendar.get(Calendar.HOUR_OF_DAY) * 60) +
                calendar.get(Calendar.MINUTE)) >= 150)) ||
                (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) ||
                ((calendar.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) &&
                (((calendar.get(Calendar.HOUR_OF_DAY) * 60) +
                calendar.get(Calendar.MINUTE)) < 210))) {
            isWeekend = true;

            return true;
        }

        isWeekend = false;

        return false;
    }

    @Override
    public String toString() {
        return arguments.toString();
    }

    public void outputString(String stringToBeWritten, boolean writeToFile,
        String fileName) {
        if (writeToFile) {
            try {
                String fullPath = new String(workingDirectory + "/workspace/").concat(fileName);
                PrintWriter writer = outputFiles.get(fileName);

                if (writer == null) {
                    writer = new PrintWriter(fullPath);
                    outputFiles.put(fileName, writer);
                }

                writer.println(stringToBeWritten);
                writer.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        context.getConsole().getOut().println(stringToBeWritten);
    }
    enum Lines {USL, 
        UFL, 
        UTL, 
        UXL, 
        LSL, 
        LFL, 
        LTL, 
        LXL;
    }
}
